// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EventBinder.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the EventBinder type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading.EventPattern
{
    using System;
    using System.Reflection;

    public class EventBinder<TArgs> where TArgs : EventArgs
    {
        #region Fields

        private readonly Action<object, EventHandler<TArgs>> _addHandler;
        private readonly Action<object, EventHandler<TArgs>> _removeHandler;

        #endregion

        private EventBinder(Action<object, EventHandler<TArgs>> addHandler,
           Action<object, EventHandler<TArgs>> removeHandler)
        {
            _addHandler = addHandler;
            _removeHandler = removeHandler;
        }

        public static EventBinder<TArgs> From<TSource>(
           Action<TSource, EventHandler<TArgs>> addHandler,
           Action<TSource, EventHandler<TArgs>> removeHandler)
        {
            #region Validate Arguments

            if (addHandler == null)
            {
                throw new ArgumentNullException("addHandler");
            }

            if (removeHandler == null)
            {
                throw new ArgumentNullException("removeHandler");
            }

            #endregion

            return new EventBinder<TArgs>(
               (o, h) => addHandler((TSource)o, h),
               (o, h) => removeHandler((TSource)o, h));
        }

        public static EventBinder<TArgs> From(Type type, string eventName)
        {
            #region Validate Arguments

            Guard.ArgumentNull("type", type);
            Guard.ArgumentNull("eventName", eventName);

            #endregion

            return From(GetEventInfo(type, eventName));
        }

        private static EventInfo GetEventInfo(Type type, string eventName)
        {
            var eventInfo = type.GetEvent(eventName);
            if (eventInfo == null)
            {
                throw new ArgumentOutOfRangeException("eventName",
                   string.Format(ExceptionMessages.EventBinder_EventNameOutOfRange, eventName, type.FullName));
            }
            return eventInfo;
        }

        public static EventBinder<TArgs> From(EventInfo eventInfo)
        {
            #region Validate Arguments

            Guard.ArgumentNull("eventInfo", eventInfo);

            #endregion

            var eventHandlerType = eventInfo.EventHandlerType;
            if (IsCompatibleEvent(eventHandlerType))
            {
                return From<object>(
                   (o, h) => eventInfo.AddEventHandler(o, Delegate.CreateDelegate(eventHandlerType, h.Target, h.Method)),
                   (o, h) => eventInfo.RemoveEventHandler(o, Delegate.CreateDelegate(eventHandlerType, h.Target, h.Method)));
            }
            return null;
        }

        private static bool IsCompatibleEvent(Type eventHandlerType)
        {
            var invokeMethod = eventHandlerType.GetMethod("Invoke");
            if (invokeMethod == null)
            {
                return false;
            }

            var parameters = invokeMethod.GetParameters();
            return parameters.Length != 2 || typeof(TArgs).IsAssignableFrom(parameters[1].ParameterType);
        }

        public void AddHandler(object source, EventHandler<TArgs> handler)
        {
            _addHandler(source, handler);
        }

        public void RemoveHandler(object source, EventHandler<TArgs> handler)
        {
            _removeHandler(source, handler);
        }
    }
}